mod builder;
mod header;
mod line;

pub use line::HttpRequestLine;
pub use header::HttpRequestHeaders;
pub use builder::{HttpRequestBuilder, HttpRequestBuildError};
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader};

use crate::http::{HttpParseError, FromBytes, HttpBody};
use super::{FetchFromBytes, HttpReceiveError, HttpSendError, IntoBytes};

#[derive(Debug, PartialEq, Eq)]
pub struct HttpRequest {
    pub line: HttpRequestLine,
    pub headers: HttpRequestHeaders,
    pub body: Option<HttpBody>,
}

impl HttpRequest {
    pub fn new(line: HttpRequestLine, headers: HttpRequestHeaders, body: Option<HttpBody>) -> Self {
        Self { line, headers, body }
    }

    pub fn builder() -> HttpRequestBuilder {
        HttpRequestBuilder::new()
    }
}

impl HttpRequest {
    pub async fn receive_from<S>(stream: &mut S) -> Result<Option<Self>, HttpReceiveError> 
    where 
        S: AsyncRead + Unpin
    {
        let mut reader = BufReader::new(stream);
        let mut buffer = Vec::new();

        // Read utill all header (meet \r\n\r\n)
        loop {
            let mut line = Vec::new();
            if reader.read_until( '\n' as u8, &mut line).await? == 0 {
                return Ok(None);
            }

            buffer.extend(line.into_iter());
            if buffer.windows(4).any(|w| w == b"\r\n\r\n") {
                break;
            }
        }

        // Parse line and buffer
        let (buffer, line) = HttpRequestLine::fetch_from_bytes(&buffer)?; 
        let (_, headers) = HttpRequestHeaders::fetch_from_bytes(buffer)?;

        // Parse body
        let content_lenght = headers.content_length.unwrap_or(0);
        let mut body = vec![0u8; content_lenght as usize];
        let body = if content_lenght > 0 {
            reader.read_exact(&mut body).await?;
            Some(HttpBody::from_bytes(body))
        } else {
            None
        };

        Ok(Some(Self::new(line, headers, body)))
    }

    pub async fn send_to<S>(&self, stream: &mut S)-> Result<(), HttpSendError> 
    where 
        S: AsyncWrite + Unpin
    {
        stream.write_all(&self.to_bytes()).await?;
        Ok(())
    }
}

impl FromBytes for HttpRequest {
    type Err = HttpParseError;
    fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err> {
        /*
            [m] [p] [v]
            [xx: xx   ]
            [xx: xx   ]

            [   body  ]
        
        */
        let (bytes, line) = HttpRequestLine::fetch_from_bytes(&bytes)?;
        let (bytes, headers) = HttpRequestHeaders::fetch_from_bytes(bytes)?;

        // Body
        let body = if bytes.len() == 0 {
            None
        } else {
            let content_length = headers.content_length;
            let body = match content_length {
                Some(content_length) => HttpBody::from_bytes(&bytes[..(content_length as usize)]),
                None => HttpBody::from_bytes(bytes),
            };
            Some(body)
        };

        Ok(Self { line, headers, body })
    }
}

impl IntoBytes for HttpRequest {
    fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::new();
        
        bytes.extend(self.line.to_bytes());
        bytes.extend(self.headers.to_bytes());
        if let Some(body) = &self.body {
            bytes.extend(body.to_bytes());
        }

        bytes 
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use crate::http::{FromBytes, HttpContentType, HttpMethod, HttpRequestHeaders, HttpRequestLine, HttpUri, HttpVersion};
    use super::HttpRequest;

    #[test]
    fn test_parse_1() {
        let bytes = b"GET /index HTTP/1.1\r\nContent-Type: application/json\r\n\r\n";
        let request: HttpRequest = FromBytes::from_bytes(bytes).unwrap();
        println!("{:?}", request);
        assert_eq!(request,
            HttpRequest {
                line: HttpRequestLine {
                    method: HttpMethod::Get,
                    uri: HttpUri { path: vec!["index".into()], query: HashMap::new() },
                    version: HttpVersion::V1_1,
                },
                headers: HttpRequestHeaders {
                    content_type: Some(HttpContentType::ApplicationJson),
                    ..Default::default()
                },
                body: None
            }
        );
    }
}